/* 
 * File:   PhysicsManager.h
 * Author: RedEyedKiller
 *
 * Created on 26 Οκτώβριος 2010, 1:59 πμ
 */

#ifndef PHYSICSMANAGER_H
#define	PHYSICSMANAGER_H

#include "../LevelMap.h"
#include "../TileProperties.h"
#include "../Vector2.h"
#include "ParticleSystemManager.h"
#include "CollisionHandler.h"
#include <algorithm>
#include <queue>
#include <vector>

namespace EntitySystem
{
class PhysicsLogic;
}

class ScriptRegister;

namespace physicsSystem
{

namespace PhysicsInternals
{
class CollisionHandler;
};

using namespace PhysicsInternals;
using namespace particleSystem;
using namespace Math;

typedef std::vector<EntitySystem::PhysicsLogic*> PhysicsPool;

const float SLEEP_THREASHOLD = 0.2;

/*
 * Stores and manages all physicsLogic objects, forces and particles of a given State.
 * PhysicsManager is also responsible for loading via ParticleParser particle systems
 * Updating Particles, performing collision check, exposing particles systems to engine
 * and compiling collision events.
 *
 * For proper operation, manager needs to have a pointer to current game map.
 */

class PhysicsManager : public particleSystem::ParticleSystemManager
{
public:

    PhysicsManager();

    virtual ~PhysicsManager();
    /**
     * This is the main method where all physics operations are performed.
     */
    void Update(unsigned int deltaTime);
    
    void SetCurrentMap(LevelMap* currentMap);
    
    LevelMap* GetCurrentMap() const;
    
    void AddPhysicsLogic(EntitySystem::PhysicsLogic* toAdd);
    
    void RemovePhysicsLogic(EntitySystem::PhysicsLogic* toRemove);
    
    void SetTileProperties(TileProperties* tileProperties);
    
    const TileProperties* GetTileProperties() const;

    /**
     * Adds the given global force with the specified id.
     * @param force The force to be added.
     * @param id Its id.
     */
    void AddGlobalForce(forces::TimedForce* force, const std::string& id);

    /**
     * Casts a ray, then it traces it to find a collision between the ray and
     * the tile map. If a collidable tile is hit by the ray, the position of
     * the collision is returned.
     *
     * @param rayStart - ray's starting position
     * @param direction - ray's direction from the starting position
     * @param distance - ray's distance
     * @return the collision point between the ray and the tile map, null vector
     *         if no collisions are detected.
     */

    template <class NumType>
    Vector2Templ<NumType> RaycastTile(const Vector2Templ<NumType> &rayStart, const Vector2Templ<NumType> &direction, float distance);


    /**
     * Casts a ray, then it traces it to find a collision between the ray and
     * the physics object. If an object is hit by the ray, the position of
     * the collision is returned.
     *
     *
     * @param rayStart - ray's starting position
     * @param direction - ray's direction from the starting position
     * @param distance - ray's distance
     * @return the collision point between the ray and the object, null vector
     *         if no collisions are detected.
     */

    template <class NumType>
    Vector2Templ<NumType> RaycastObject(const Vector2Templ<NumType> &rayStart, const Vector2Templ<NumType> &direction, float distance);


protected:
    //forces
    void ApplyForces(float seconds);
    //physicsLogic
    void UpdatePhysicsPool(unsigned int time);

private:
    typedef std::map<std::string, forces::TimedForce*> GlobalForcesPool;
    
    PhysicsInternals::CollisionHandler collisionHandler;
    
    /**
     * The data structure which contains all physical objects of entities.
     */
    PhysicsPool physicsPool;
    
    /**
     * The data structure which contains all global forces.(as gravity or drag)
     */
    GlobalForcesPool gForcePool;

    /**
     * The tile properties currentMap has. Required for collision resolution.
     */
    TileProperties* tileProperties;
    
    /**
     * The LevelMap which represents the game world.
     */
    LevelMap* currentMap;

};

/**
 * Casts a ray, then it traces it to find a collision between the ray and
 * the tile map. If a collidable tile is hit by the ray, the position of
 * the collision is returned.
 *
 * @param rayStart - ray's starting position
 * @param direction - ray's direction from the starting position
 * @param distance - ray's distance
 * @return the collision point between the ray and the tile map, null vector
 *         if no collisions are detected.
 */

template <class NumType>
Vector2Templ<NumType> PhysicsManager::RaycastTile(const Vector2Templ<NumType> &rayStart, const Vector2Templ<NumType> &direction, float distance)
{
    Vector2Templ<NumType> nullPoint(0, 0);
    Vector2Templ<NumType> collisionPoint;
    bool collisionFound = false;
    int tileWidth, tileHeight;
    int mapWidth, mapHeight;


    //calculate the ray's end
    Vector2Templ<NumType> rayEnd(rayStart + direction.Normal() * distance);

    //if a coordinate of the ray's start is negative then stop the process
    if(rayStart.GetX() < 0 || rayStart.GetY() < 0)
    {
        return nullPoint;
    }

    //if a tile map has not been defined yet
    if(currentMap == NULL)
    {
        //indicate that no collisions with the tile map can happen
        return nullPoint;
    }

    tileWidth = currentMap->GetTileWidth();
    tileHeight = currentMap->GetTileHeight();
    mapWidth = currentMap->GetWidth() * tileWidth;
    mapHeight = currentMap->GetHeight() * tileHeight;

    //get the starting and ending points in integer format
    int x0 = static_cast<int> (rayStart.GetX());
    int y0 = static_cast<int> (rayStart.GetY());
    int x1 = static_cast<int> (rayEnd.GetX());
    int y1 = static_cast<int> (rayEnd.GetY());

    //check if the ray is vertical
    bool vertical = x0 == x1;
    //check if the ray is horizontal
    bool horizontal = y0 == y1;

    float raySlope = 0.0; //a in y = ax + b
    float rayConstant = 0.0; //b in y = ax + b

    //if the ray is not vertical
    if(!vertical)
    {
        //calculate ray slope
        raySlope = (y1 - y0) / static_cast<float> (x1 - x0);
        //calculate ray constant
        rayConstant = (y0 * x1 - y1 * x0) / static_cast<float> (x1 - x0);
    }

    //The level map is divided in a grid with rectangles having width equal
    //to the tile width and height equal to the tile height. In order to
    //check for tile collisions we don't need to check every pixel of the level
    //map. Instead we need to find where the ray intersects the grid's lines.
    //
    //For intersections between the ray and gird lines that are perpendicular to
    //the x axis, we know the exact x value of the intersection and we can calculate
    //the y value by using the y = a * x + b formula
    //For intersections between the ray and grid lines that are parallel to the
    //x axis, we know the exact y value of the intersection and we can calculate
    //the x value by using the x = (y - b) / a formula.

    int startingX = (x0 / tileWidth) * tileWidth;
    int endingX = (x1 / tileWidth) * tileWidth;
    int startingY = (y0 / tileHeight) * tileHeight;
    int endingY = (y1 / tileHeight) * tileHeight;

    bool leftToRight = x0 < x1;
    bool downToTop = y0 < y1;

    //if the ray is horizontal
    if(horizontal)
    {
        //y values are always equal to the rayConstant
        int intRayConstant = static_cast<int> (rayConstant);
        //if the ray is running from left to right
        if(leftToRight)
        {

            //from 'starting x' to 'ending x' and adding the tile width as a step
            for(int x = startingX; x <= endingX && !collisionFound; x += tileWidth)
            {
                if(x >= mapWidth)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(rayConstant, x);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point
                    collisionPoint.Set(x, intRayConstant);
                    //indicate that a collision was found
                    collisionFound = true;
                }
            }
        }
        else
        {
            //if the ray is running from right to left
            //from 'starting x' to 'ending x' and subtructing the width as a step
            for(int x = startingX; x >= endingX && !collisionFound; x -= tileWidth)
            {
                if(x < 0)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(rayConstant, x);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point
                    collisionPoint.Set(x, intRayConstant);
                    //indicate that a collision was found
                    collisionFound = true;
                }
            }
        }
    }
    else if(vertical)
    {//if the ray is vertical
        //x values are always equal to x0 and x1
        //if the ray is running from down to top
        if(downToTop)
        {
            //from 'starting y' to 'ending y' and adding the tile height as a step
            for(int y = startingY; y <= endingY && !collisionFound; y += tileHeight)
            {
                if(y >= mapHeight)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(y, x0);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point
                    collisionPoint.Set(x0, y);
                    //indicate that a collision was found
                    collisionFound = true;
                }
            }
        }
        else
        {
            //from 'starting y' to 'ending y' and adding the tile height as a step
            for(int y = startingY; y >= endingY && !collisionFound; y -= tileHeight)
            {
                if(y < 0)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(y, x0);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point
                    collisionPoint.Set(x0, y);
                    //indicate that a collision was found
                    collisionFound = true;
                }
            }
        }
    }
    else
    {
        //if the ray is neither vertical nor horizontal
        //we need to search for the first intersection that hits a collidable tile
        //for both grid lines that are perpendicular and parallel to the x axis.
        //We find the first intersection for each of the two cases and the one
        //that has the least distance from the ray's start is the collision
        //intersection

        if(direction.GetX() > 0)
        {
            startingX += tileWidth;
        }

        if(direction.GetY() > 0)
        {
            startingY += tileHeight;
        }

        Vector2F perpendicularCollisionPoint(0, 0);
        Vector2F parellelCollisionPoint(0, 0);

        //if the ray is running from left to right
        if(leftToRight)
        {
            //from 'starting x' to 'ending x' and adding the tile width as a step
            for(int x = startingX; x <= endingX; x += tileWidth)
            {
                //calculate y value
                int y = raySlope * x + rayConstant;

                if(x > mapWidth || y < 0 || y >= mapHeight)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(y, x);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point as the first collision point with
                    //grid lines perpendicular to the x axis
                    perpendicularCollisionPoint.Set(x, y);
                    //stop searching for other intersections
                    break;
                }
            }
        }
        else
        {
            //if the ray is running from right to left
            //from 'starting x' to 'ending x' and subtructing the width as a step
            for(int x = startingX; x >= endingX; x -= tileWidth)
            {
                //calculate y value
                int y = raySlope * x + rayConstant;

                if(x < 0 || y < 0 || y >= mapHeight)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(y, x);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point as the first collision point with
                    //grid lines perpendicular to the x axis
                    perpendicularCollisionPoint.Set(x, y);
                    //stop searching for other intersections
                    break;
                }
            }
        }

        //if the ray is running from down to top
        if(downToTop)
        {
            //from 'starting y' to 'ending y' and adding the tile height as a step
            for(int y = startingY; y <= endingY; y += tileHeight)
            {
                //calculate x value
                int x = (y - rayConstant) / raySlope;

                if(y > mapHeight || x < 0 || x >= mapWidth)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(y, x);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point as the first collision point with
                    //grid lines parallel to the x axis
                    parellelCollisionPoint.Set(x, y);
                    //stop searching for other intersections
                    break;
                }
            }
        }
        else
        {
            //if the ray is running from top to down
            //from 'starting y' to 'ending y' and subtructing the tile heisgh as a step
            for(int y = startingY; y >= endingY; y -= tileHeight)
            {
                //calculate x value
                int x = (y - rayConstant) / raySlope;

                if(y < 0 || x < 0 || x >= mapWidth)
                    break;

                //get the tile properties belonging to the current intersection
                unsigned long tileFlag = currentMap->GetTilelistPropsFromRPos(y, x);
                //if the tile belonging to the current intersection is collidable
                if((tileFlag & 1) != 0)
                {
                    //store the collision point as the first collision point with
                    //grid lines parallel to the x axis
                    parellelCollisionPoint.Set(x, y);
                    //stop searching for other intersections
                    break;
                }
            }
        }

        //if a collision point was found for either the parellel or the perpendicular to x axis grid lines
        if(!parellelCollisionPoint.IsNull() || !perpendicularCollisionPoint.IsNull())
        {
            //if no parellel collision is found
            if(parellelCollisionPoint.IsNull())
            {
                //the collision point is the perpendicular one
                collisionPoint.Set(perpendicularCollisionPoint);
            }
            else if(perpendicularCollisionPoint.IsNull())
            {
                //if no perpendicular collision is found
                //the collision point is the parellel one
                collisionPoint.Set(parellelCollisionPoint);
            }
            else
            {
                //the collision point is the one with the smallest distance
                //from the ray's start
                perpendicularCollisionPoint.Distance(rayStart) < parellelCollisionPoint.Distance(rayStart) ?
                        collisionPoint.Set(perpendicularCollisionPoint)
                        :
                        collisionPoint.Set(parellelCollisionPoint)
                        ;
            }

            collisionFound = true;
        }
    }

    //return
    return(collisionFound) ? collisionPoint : nullPoint;
}

/**
 * Casts a ray, then it traces it to find a collision between the ray and
 * the physics object. If an object is hit by the ray, the position of
 * the collision is returned.
 *
 *
 * @param rayStart - ray's starting position
 * @param direction - ray's direction from the starting position
 * @param distance - ray's distance
 * @return the collision point between the ray and the object, null vector
 *         if no collisions are detected.
 */

class ComparePhysicsObjects : public binary_function< PhysicsLogic*, PhysicsLogic*, bool>
{
private:
    bool m_xAxis;
    bool m_ascending;

public:

    /**
     * Initializes some parameters of the comparison.
     *
     * @param xAxis - sorting will occur based on x axis positions if true, y axis positions if false.
     * @param ascending - sorting using this functor will result in an ascending order of the physics objects.
     */

    ComparePhysicsObjects(bool xAxis, bool ascending = true);


    bool operator() (PhysicsLogic *left, PhysicsLogic *right);
};

template <class NumType>
Vector2Templ<NumType> PhysicsManager::RaycastObject(const Vector2Templ<NumType> &rayStart, const Vector2Templ<NumType> &direction, float distance)
{
    Vector2Templ<NumType> nullPoint(0, 0);
    Vector2Templ<NumType> collisionPoint;
    bool collisionFound = false;


    //calculate the ray's end
    Vector2Templ<NumType> rayEnd(rayStart + direction.Normal() * distance);

    //if a coordinate of the ray's start is negative then stop the process
    if(rayStart.GetX() < 0 || rayStart.GetY() < 0)
    {
        return nullPoint;
    }

    //get the starting and ending points in integer format
    int x0 = static_cast<int> (rayStart.GetX());
    int y0 = static_cast<int> (rayStart.GetY());
    int x1 = static_cast<int> (rayEnd.GetX());
    int y1 = static_cast<int> (rayEnd.GetY());

    bool leftToRight = x0 < x1;
    bool downToTop = y0 < y1;

    //check if the ray is vertical
    bool vertical = x0 == x1;
    //check if the ray is horizontal
    bool horizontal = y0 == y1;

    float raySlope = 0.0; //a in y = ax + b
    float rayConstant = 0.0; //b in y = ax + b

    //if the ray is not vertical
    if(!vertical)
    {
        //calculate ray slope
        raySlope = (y1 - y0) / static_cast<float> (x1 - x0);
        //calculate ray constant
        rayConstant = (y0 * x1 - y1 * x0) / static_cast<float> (x1 - x0);
    }


    //if the ray is horizontal
    if(horizontal)
    {
        //search for potential collisions along the y axis
        int physicsObjectCount = physicsPool.size();
        std::vector< PhysicsLogic* > potentialCollisions;
        int x, y, width, height;

        //if the line is running from the right to the left
        if(leftToRight)
        {
            std::priority_queue< PhysicsLogic*, std::vector< PhysicsLogic* >, ComparePhysicsObjects > potentialCollisions(ComparePhysicsObjects(true, false));

            for(int i = 0; i < physicsObjectCount; ++i)
            {
                width = physicsPool[i]->GetRect()->GetWidth();
                height = physicsPool[i]->GetRect()->GetHeight();
                x = physicsPool[i]->GetPosition().GetX();
                y = physicsPool[i]->GetPosition().GetY();

                //potential collisions include the objects that rest along the horizontal ray
                //and since the line is running from left to right we only need to check for
                //objects that have an x greater than x0
                if(x > x0 && x <= x1 && rayConstant >= y && rayConstant <= y + height)
                {
                    potentialCollisions.push(physicsPool[i]);
                }
            }

            //if the potential collisions vector is not empty
            if(!potentialCollisions.empty())
            {
                //the first potential collision in the priority queue, is the hit
                collisionFound = true;
                collisionPoint.Set(potentialCollisions.top()->GetPosition().GetX(), rayConstant);
            }
        }
        else
        {
            std::priority_queue< PhysicsLogic*, std::vector< PhysicsLogic* >, ComparePhysicsObjects > potentialCollisions(ComparePhysicsObjects(true));

            for(int i = 0; i < physicsObjectCount; ++i)
            {
                width = physicsPool[i]->GetRect()->GetWidth();
                height = physicsPool[i]->GetRect()->GetHeight();
                x = physicsPool[i]->GetParent()->GetPositionX();
                y = physicsPool[i]->GetParent()->GetPositionY();

                //potential collisions include the objects that rest along the horizontal ray
                //and since the line is running from left to right we only need to check for
                //objects that have an x less than x0
                if(x + width < x0 && x + width > x1 && rayConstant >= y && rayConstant <= y + height)
                {
                    potentialCollisions.push(physicsPool[i]);
                }
            }


            //if the potential collisions vector is not empty
            if(!potentialCollisions.empty())
            {
                //the first potential collision in the priority queue, is the hit
                collisionFound = true;
                collisionPoint.Set(potentialCollisions.top()->GetPosition().GetX() + potentialCollisions.top()->GetRect()->GetWidth(), rayConstant);
            }
        }
    }
    else if(vertical)
    {
        //search for potential collisions along the x axis
        int physicsObjectCount = physicsPool.size();
        int x, y, width, height;

        //if the line is running from down to the top
        if(downToTop)
        {
            std::priority_queue< PhysicsLogic*, std::vector< PhysicsLogic* >, ComparePhysicsObjects > potentialCollisions(ComparePhysicsObjects(false, false));

            for(int i = 0; i < physicsObjectCount; ++i)
            {
                width = physicsPool[i]->GetRect()->GetWidth();
                height = physicsPool[i]->GetRect()->GetHeight();
                x = physicsPool[i]->GetPosition().GetX();
                y = physicsPool[i]->GetPosition().GetY();

                //potential collisions include the objects that rest along the vertical ray
                //and since the line is running from down to top we only need to check for
                //objects that have a y greater than y0
                if(y > y0 && y <= y1 && x0 >= x && x0 <= x + width)
                {
                    potentialCollisions.push(physicsPool[i]);
                }
            }

            //if the potential collisions vector is not empty
            if(!potentialCollisions.empty())
            {
                //the first potential collision in the priority queue
                collisionFound = true;
                collisionPoint.Set(float(x0), potentialCollisions.top()->GetPosition().GetY());
            }
        }
        else
        {
            std::priority_queue< PhysicsLogic*, std::vector< PhysicsLogic* >, ComparePhysicsObjects > potentialCollisions(ComparePhysicsObjects(false));

            for(int i = 0; i < physicsObjectCount; ++i)
            {
                width = physicsPool[i]->GetRect()->GetWidth();
                height = physicsPool[i]->GetRect()->GetHeight();
                x = physicsPool[i]->GetParent()->GetPositionX();
                y = physicsPool[i]->GetParent()->GetPositionY();

                //potential collisions include the objects that rest along the horizontal ray
                //and since the line is running from left to right we only need to check for
                //objects that have an x less than x0
                if(y + height < y0 && y + height > y1 && x0 >= x && x0 <= x + width)
                {
                    potentialCollisions.push(physicsPool[i]);
                }
            }


            //if the potential collisions vector is not empty
            if(!potentialCollisions.empty())
            {
                //the first potential collision in the sorted vector, is the hit
                collisionFound = true;
                collisionPoint.Set(float(x0) , potentialCollisions.top()->GetPosition().GetY() + potentialCollisions.top()->GetRect()->GetHeight());
            }
        }
    }


    return(collisionFound) ? collisionPoint : nullPoint;
}


};
#endif	/* PHYSICSMANAGER_H */
